package fs

import (
	"database/sql"
	"errors"
	"gitee.com/tziye/renai-go/common"
	"gitee.com/tziye/renai-go/renai"
	"gitee.com/tziye/renai-go/renai/id"
	"gitee.com/tziye/renai-go/sqlproxy"
	"sort"
	"time"
)

type FsOperator struct {
	RenaiClient *renai.RenaiClient
	Db          *sql.DB
}

func NewFsOperator(renaiClient *renai.RenaiClient) *FsOperator {
	return &FsOperator{
		RenaiClient: renaiClient,
		Db:          renaiClient.Db,
	}
}

func (fsOperator *FsOperator) SaveWithMetadata(module string, metadata map[string]string, content []byte) int64 {
	businessId := generateId(fsOperator.RenaiClient)
	renaiFile := RenaiFile{
		Module:     module,
		BusinessId: businessId,
		Metadata:   metadata,
		Content:    content,
	}
	fileList := splitContent(renaiFile)
	metadataStr := common.String(metadata)

	s := "INSERT INTO renai_file_system(module, business_id, seq, metadata, content) VALUES(?, ?, ?, ?, ?)"
	length := len(fileList)
	ch := make(chan any, length)
	for _, file := range fileList {
		cur := file
		go func() {
			result := sqlproxy.Exec(fsOperator.Db, s, cur.Module, cur.BusinessId, cur.Seq, metadataStr, cur.Content)
			ch <- result
		}()
	}
	for i := 0; i < length; i++ {
		if common.ChanGet(ch, 10) == nil {
			common.PanicErr(errors.New("save error"))
		}
	}
	return businessId
}

func (fsOperator *FsOperator) Save(module string, content []byte) int64 {
	return fsOperator.SaveWithMetadata(module, make(map[string]string), content)
}

func (fsOperator *FsOperator) Query(id int64) RenaiFile {
	s := "SELECT * FROM renai_file_system WHERE business_id = ?"
	files := fsOperator.doQuery(s, id)
	return mergeContent(files)
}

func (fsOperator *FsOperator) QueryByModule(module string) []RenaiFile {
	s := "SELECT * FROM renai_file_system WHERE module = ?"
	files := fsOperator.doQuery(s, module)
	fileMap := make(map[int64][]RenaiFile)
	for _, file := range files {
		fs := fileMap[file.BusinessId]
		fileMap[file.BusinessId] = append(fs, file)
	}
	result := make([]RenaiFile, 0)
	for _, files := range fileMap {
		result = append(result, mergeContent(files))
	}
	return result
}

func (fsOperator *FsOperator) doQuery(s string, param any) []RenaiFile {
	rows := sqlproxy.QueryRows(fsOperator.Db, s, param)
	defer rows.Close()
	files := make([]RenaiFile, 0)
	for rows.Next() {
		var file RenaiFile
		var metadataStr string
		err := rows.Scan(&file.Id, &file.Module, &file.BusinessId, &file.Seq, &metadataStr,
			&file.Content, &file.CreateTime, &file.UpdateTime)
		common.PanicErr(err)
		var metadata map[string]string
		common.Parse(metadataStr, &metadata)
		file.Metadata = metadata
		files = append(files, file)
	}
	return files
}

func (fsOperator *FsOperator) Delete(id int64) bool {
	s := "DELETE FROM renai_file_system WHERE business_id = ?"
	rows := sqlproxy.Exec(fsOperator.Db, s, id)
	return rows > 0
}

func (fsOperator *FsOperator) DeleteByModule(module string) int {
	s := "DELETE FROM renai_file_system WHERE module = ?"
	return sqlproxy.Exec(fsOperator.Db, s, module)
}

const splitSize = 15 * 1024 * 1024

func generateId(renaiClient *renai.RenaiClient) int64 {
	idOperator := id.NewIdOperator(renaiClient)
	idSpec := id.NewRenaiIdSpecWithPattern("RenaiFS", id.EPOCHM)
	return idOperator.GenerateId(idSpec)
}

func splitContent(renaiFile RenaiFile) []RenaiFile {
	content := renaiFile.Content
	fileList := make([]RenaiFile, 0)
	length := len(content)
	size := splitSize
	seq := 0
	for i := 0; size == splitSize; i += splitSize {
		size = common.Min(length-i, splitSize)
		if size <= 0 {
			break
		}
		cur := make([]byte, size)
		copy(cur, content[i:size])
		newFile := renaiFile.copy(cur)
		newFile.Seq = seq
		seq++
		fileList = append(fileList, newFile)
	}
	return fileList
}

func mergeContent(fileList []RenaiFile) RenaiFile {
	var renaiFile RenaiFile
	if len(fileList) == 0 {
		return renaiFile
	}
	sort.Sort(BySeq(fileList))
	content := make([]byte, 0)
	for _, file := range fileList {
		content = append(content, file.Content...)
	}
	renaiFile = fileList[0]
	return renaiFile.copy(content)
}

type RenaiFile struct {
	Id         int64
	Module     string
	BusinessId int64
	Metadata   map[string]string
	Seq        int
	Content    []byte
	CreateTime time.Time
	UpdateTime time.Time
}

func (renaiFile *RenaiFile) Size() int {
	return len(renaiFile.Content)
}

func (renaiFile *RenaiFile) copy(content []byte) RenaiFile {
	newFile := RenaiFile{
		Id:         renaiFile.Id,
		Module:     renaiFile.Module,
		BusinessId: renaiFile.BusinessId,
		Metadata:   renaiFile.Metadata,
		Seq:        renaiFile.Seq,
		CreateTime: renaiFile.CreateTime,
	}
	newFile.Content = content
	return newFile
}

type BySeq []RenaiFile

func (a BySeq) Len() int           { return len(a) }
func (a BySeq) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a BySeq) Less(i, j int) bool { return a[i].Seq < a[j].Seq }
